﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using FastDFS.Client;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using Polly;
using Polly.Retry;
using CK.Sprite.FileStorage.Application;

namespace CK.Sprite.FileStorage
{
    public static class FileStorageExtension
    {
        private static readonly RetryPolicy _storageRetry;

        static FileStorageExtension()
        {
            //_storageRetry = Polly.Policy
            //    .Handle<FdfsException>()
            //    .RetryAsync(2);
        }


        /// <summary>
        /// [POST] Upload file
        /// QueryString:
        ///     ext      : string, file extension, without '.'. 
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public static async Task UploadAsync(HttpContext context)
        {
            if (context.Request.Method != HttpMethods.Post)
            {
                await CreateResponseAsync(context, -2, null, "请使用POST上传文件");
                return;
            }

            string strFileStoreId = context.Request.Headers["FileStoreId"];
            if(strFileStoreId == null)
            {
                await CreateResponseAsync(context, -2, null, "请传递上传服务地址");
                return;
            }

            var fileStoreId = int.Parse(strFileStoreId.ToString());

            if(fileStoreId == 0)
            {
                fileStoreId = 1;
            }

            var provider = await FileStorageHelper.GetStorageProviderByIdAsync(fileStoreId);
            if (context.Request.Form.Files == null
                || context.Request.Form.Files.Count == 0)
            {
                await CreateResponseAsync(context, -3, null, "未上传有效文件");
                return;
            }

            LogHelper.FileLogHelper.LogInformation(
                $"[upload request, file count: {context.Request.Form.Files.Count}");

            try
            {
                DateTime nowTime = DateTime.Now;
                var resultFiles = new List<UploadFileInfo>();
                foreach (var file in context.Request.Form.Files)
                {
                    var ext = file.FileName.Substring(file.FileName.LastIndexOf(".") + 1).ToLower();
                    string bucket = null;
                    if (provider is OSSStorageProvider)
                    {
                       var option =  provider.Options as OSSStorageOptions;
                       bucket = option.Bucket;
                    }
                    var result = await SaveFileAsync(context, file, provider, ext, bucket);

                    if (result != null)
                    {
                        resultFiles.Add(new UploadFileInfo()
                        {
                            FileExt = ext,
                            FileName = file.FileName,
                            FileSize = file.Length,
                            ServerFilePath = result,
                            UploadTime = nowTime
                        });
                    }
                }

                await CreateResponseAsync(context, 0, resultFiles);
            }
            catch (System.Exception ex)
            {
                LogHelper.FileLogHelper.LogError($"[upload error. {GetError(ex)}");
                await CreateResponseAsync(context, -500, null, "服务器发生未知异常");
            }
        }

        private static async Task<string> SaveFileAsync(HttpContext context, IFormFile file, StorageProvider provider, string ext, string bucket)
        {
            var filename = ContentDispositionHeaderValue
                .Parse(file.ContentDisposition)
                .FileName;

            LogHelper.FileLogHelper.LogDebug($"[upload request start, file: {filename}, length: {file.Length}");

            var inputStream = file.OpenReadStream();
            string result = "";

            //upload master file
            string[] masterResult = null;
            //await _storageRetry.ExecuteAsync(async () =>
            //                   {
            //                       masterResult = await provider.StoreAsync(inputStream, ext, null);
            //                       result = masterResult[0];
            //                   });

            masterResult = await provider.StoreAsync(inputStream, ext, bucket);
            result = masterResult[1];

            LogHelper.FileLogHelper.LogDebug($"[upload request complete, url: {string.Join(",", result)}");
            return result;
        }

        private static async Task CreateResponseAsync(HttpContext context, int code, List<UploadFileInfo> files,
            string message = "ok")
        {
            if (code < 0) context.Response.StatusCode = 500;
            context.Response.ContentType = "application/json;charset=utf-8";
            var result = files?.Count > 0 ? "[\"" + string.Join("\",\"", files) + "\"]" : "null";
            //var requestId = context.GetRequestId();
            UploadResult uploadResult = new UploadResult()
            {
                code = code,
                message = message,
                files = files
            };
            var body = JsonConvert.SerializeObject(uploadResult);

            await context.Response.WriteAsync(body);

            if (code >= 0)
            {
                LogHelper.FileLogHelper.LogInformation($"[upload response: {body}");
            }
            else
            {
                LogHelper.FileLogHelper.LogError($"upload error: {message}");
            }
        }

        private static string GetError(Exception ex)
        {
            var divider = "-->";
            var exceptionMessage = ex.Message;
            var deep = 3;
            var innerException = ex.InnerException;
            while (innerException != null && deep > 0)
            {
                exceptionMessage += divider + innerException.Message;
                innerException = innerException.InnerException;
                deep--;
            }

            return ex.Message + divider + exceptionMessage;
        }
    }
}
